package com.dyna.chat.websocket;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.dyna.chat.domain.dto.WSAuthorize;
import com.dyna.chat.domain.req.ws.WSBaseReq;
import com.dyna.chat.enums.WSReqTypeEnum;
import com.dyna.chat.service.WebSocketService;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;


/**
 * @author Admin
 */
@Slf4j
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private WebSocketService webSocketService;

    /**
     * 当web客户端连接后，触发该方法
     * @param ctx 上下文对象
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        log.info("用户【{}】上线啦", ctx.channel().id());
        // 初始化webSocketService
        this.webSocketService = getService();
    }

    private WebSocketService getService() {
        return SpringUtil.getBean(WebSocketService.class);
    }

    /**
     * 客户端离线
     * @param ctx 上下文
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 可能和下面出现的问题有关
        userOffLine(ctx);
    }

    /**
     * 取消绑定
     * @param ctx 上下文
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // 可能出现业务判断离线后再次触发 channelInactive
        log.warn("触发 channelInactive 掉线![{}]", ctx.channel().id());
        userOffLine(ctx);
    }

    private void userOffLine(ChannelHandlerContext ctx) {
        // TODO 用户离线
//        this.webSocketService.removed(ctx.channel());
        ctx.channel().close();
    }

    /**
     * 1.心跳检查
     * 15秒没有检测到新的消息那么就离线
     * 当触发的事件evt是IdleStateEvent类型时，表示这是一个关于通道空闲状态的事件。
     * 通过判断idleStateEvent.state()是否等于IdleState.READER_IDLE来确定当前是“读取空闲”状态。这意味着在指定时间内没有从客户端收到任何数据（即读操作处于空闲状态）。
     * 如果确实处于读取空闲状态，则调用userOffLine(ctx)方法，这通常意味着关闭用户的连接，因为可能认为客户端已经离线或者网络出现问题。
     * 2.WebSocket握手
     * WebSocketServerProtocolHandler.HandshakeComplete的作用是发起连接
     *
     * @param ctx 上下文
     * @param evt 事件
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 心跳检测
        if (evt instanceof IdleStateEvent) {
            log.info("心跳检测到离线...");
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            // 读空闲
            if (idleStateEvent.state() == IdleState.READER_IDLE) {
                // 关闭用户的连接
                userOffLine(ctx);
            }
        } else if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
            // Websocket握手连接
            log.info("websocket create connect success ...");
            this.webSocketService.connect(ctx.channel());
            String token = NettyUtil.getAttr(ctx.channel(), NettyUtil.TOKEN);
            // 为什么抹茶不在这个地方处理token为空的情况呢?因为需要通过websocket获取登录验证码，因此并不需要验证不存在token的情况
            if (StrUtil.isNotBlank(token)) {
                this.webSocketService.authorize(ctx.channel(), new WSAuthorize(token));
            }
        }
        super.userEventTriggered(ctx, evt);
    }

    /**
     * 处理异常
     * @param ctx 上下文对象
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.warn("异常发生，异常消息 ={}", JSON.toJSONString(cause));
        ctx.channel().close();
    }

    /**
     * 读取客户端发送的请求报文
     * @param ctx 上下文
     * @param msg 消息
     * @throws Exception e
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        log.info("读取客户端发送的请求报文:{}", msg.text());
        WSBaseReq wsBaseReq = JSONUtil.toBean(msg.text(), WSBaseReq.class);
        WSReqTypeEnum wsReqTypeEnum = WSReqTypeEnum.of(wsBaseReq.getType());
        switch (wsReqTypeEnum) {
            case LOGIN:
                this.webSocketService.handleLoginReq(ctx.channel());
                log.info("请求二维码 = " + msg.text());
                break;
            case HEARTBEAT:
                break;
            default:
                log.info("未知类型");
        }
    }
}
